/**
 * Class: DPoint3
 *
 * Purpose: 
 *
 * Created: Jan 23, 2010 @ 11:06:19 PM
 * @author Ken Scott
 */
package org.openfantasia.util.math;

import org.openfantasia.util.Copyable;

import java.util.Arrays;

public class DPoint3 implements Copyable<DPoint3> {

	public static final DPoint3 ORIGIN = new DPoint3(0, 0, 0);

	public static final DPoint3 UNITY = new DPoint3(1, 1, 1);

	public static final DPoint3 INVALID = new DPoint3(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE);

	private static final int N = 3;

	public double p[];


	public DPoint3() {
		p = new double[N];
	}


	public DPoint3(double p0, double p1, double p2) {
		p = new double[]{p0, p1, p2};
	}


	public DPoint3(double[] a) {
		if (a.length != N) {
			throw new IllegalArgumentException("Input array must be of length " + N + ", not " + a.length);
		}
		double[] result = new double[a.length];
		System.arraycopy(a, 0, result, 0, a.length);
		this.p = result;
	}


	public DPoint3(DPoint3 pt) {
		this(pt.p);
	}


	public DPoint3 copy() {
		return new DPoint3(p);
	}


	public void copy(DPoint3 that) {
		p = Math1D.copy(that.p);
	}


	public static DPoint3[] empty(DPoint3... src) {
		return new DPoint3[src.length];
	}


	public static DPoint3[] copy(DPoint3... vector) {
		DPoint3[] result = empty(vector);
		for (int i = 0; i < vector.length; i++) {
			result[i] = vector[i].copy();
		}
		return result;
	}


	public DPoint3 clear() {
		Math1D.fill(p, 0);
		return this;
	}


	public boolean isOrigin() {
		return p[0] == 0 && p[1] == 0 && p[2] == 0;
	}


	public boolean isValid() {
		return (p[0] != Double.MIN_VALUE) && (p[1] != Double.MIN_VALUE) && (p[2] != Double.MIN_VALUE);
	}


	public DPoint3 set(double... px) {
		Math1D.copy(p, px);
		return this;
	}


	public DPoint3 set(DPoint3 other) {
		Math1D.copy(p, other.p);
		return this;
	}


	public double getX() {
		return p[0];
	}


	public void setX(double v) {
		p[0] = v;
	}


	public double getY() {
		return p[1];
	}


	public void setY(double v) {
		p[1] = v;
	}


	public double getZ() {
		return p[2];
	}


	public void setZ(double v) {
		p[2] = v;
	}


	public DPoint3 randomize(double mag) {
		p[0] = PMath.random(-1.0, 1.0);
		p[1] = PMath.random(-1.0, 1.0);
		p[2] = PMath.random(-1.0, 1.0);
		return normalize().scale(mag);
	}


	public double dotp(DPoint3 other) {
		return Math1D.dotp(this.p, other.p);
	}


	public DPoint3 mulNoise(double frac) {
		this.p[0] *= PMath.random(-frac, frac);
		this.p[1] *= PMath.random(-frac, frac);
		this.p[2] *= PMath.random(-frac, frac);
		return this;
	}


	public DPoint3 addNoise(double frac) {
		this.p[0] += PMath.random(-frac, frac);
		this.p[1] += PMath.random(-frac, frac);
		this.p[2] += PMath.random(-frac, frac);
		return this;
	}


	public DPoint3 normalize() {
		double mag = Math1D.mag(p);
		this.p[0] /= mag;
		this.p[1] /= mag;
		this.p[2] /= mag;
		return this;
	}


	public double mag() {
		return PMath.mag(p[0], p[1], p[2]);
	}


	public DPoint3 neg() {
		this.p[0] = -this.p[0];
		this.p[1] = -this.p[1];
		this.p[2] = -this.p[2];
		return this;
	}


	public DPoint3 scale(double a) {
		this.p[0] *= a;
		this.p[1] *= a;
		this.p[2] *= a;
		return this;
	}


	public DPoint3 scale(DPoint3 a) {
		return mul(a);
	}


	public DPoint3 abs() {
		this.p[0] = Math.abs(this.p[0]);
		this.p[1] = Math.abs(this.p[1]);
		this.p[2] = Math.abs(this.p[2]);
		return this;
	}


	public DPoint3 add(double... a) {
		if (a == null) {
			return this;
		}

		this.p[0] += a[0];
		this.p[1] += a[1];
		this.p[2] += a[2];
		return this;
	}


	public DPoint3 add(DPoint3 a) {
		if (a == null) {
			return this;
		}

		return add(a.p);
	}


	public DPoint3 sub(double... a) {
		if (a == null) {
			return this;
		}

		this.p[0] -= a[0];
		this.p[1] -= a[1];
		this.p[2] -= a[2];
		return this;
	}


	public DPoint3 sub(DPoint3 a) {
		if (a == null) {
			return this;
		}

		return sub(a.p);
	}


	public DPoint3 mul(double... a) {
		if (a == null) {
			return this;
		}

		this.p[0] *= a[0];
		this.p[1] *= a[1];
		this.p[2] *= a[2];
		return this;
	}


	public DPoint3 mul(DPoint3 a) {
		if (a == null) {
			return this;
		}

		return mul(a.p);
	}


	public DPoint3 div(double... a) {
		if (a == null) {
			return this;
		}

		this.p[0] /= a[0];
		this.p[1] /= a[1];
		this.p[2] /= a[2];
		return this;
	}


	public DPoint3 div(DPoint3 a) {
		if (a == null) {
			return this;
		}

		return div(a.p);
	}


	void rotateX(float val) {
		double cosval = Math.cos(val);
		double sinval = Math.sin(val);
		p[1] = p[1] * cosval - p[2] * sinval;
		p[2] = p[1] * sinval + p[2] * cosval;
	}


	void rotateY(float val) {
		double cosval = Math.cos(val);
		double sinval = Math.sin(val);
		p[0] = p[0] * cosval - p[2] * sinval;
		p[2] = p[0] * sinval + p[2] * cosval;
	}


	void rotateZ(float val) {
		double cosval = Math.cos(val);
		double sinval = Math.sin(val);
		p[0] = p[0] * cosval - p[1] * sinval;
		p[1] = p[0] * sinval + p[1] * cosval;
	}


	public double[] toDouble() {
		return new double[]{p[0], p[1], p[2]};
	}


	public float[] toFloat() {
		return new float[]{(float) p[0], (float) p[1], (float) p[2]};
	}


	public int[] toInt() {
		return new int[]{(int) p[0], (int) p[1], (int) p[2]};
	}


	@Override
	public String toString() {
		return Math1D.toString(p);
	}


	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (!(o instanceof DPoint3)) {
			return false;
		}

		DPoint3 that = (DPoint3) o;

		if (!Arrays.equals(p, that.p)) {
			return false;
		}

		return true;
	}


	@Override
	public int hashCode() {
		return p != null ? Arrays.hashCode(p) : 0;
	}


	public double[] to3D() {
		double[] result = new double[4];
		System.arraycopy(p, 0, result, 0, 3);
		result[3] = 1;
		return result;
	}


	public void from3D(double[] m) {
		System.arraycopy(m, 0, p, 0, 3);
	}
}
